#pragma once

#include <vector>
#include <string>
#include <mutex>
#include <fstream>
#include <jsoncpp/json/json.h>

#include "oj_model_mysql.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/utils.hpp"
#include "../comm/httplib.h"

namespace ns_control
{
    using namespace ns_model_mysql;
    using namespace ns_view;
    using namespace ns_log;
    using namespace ns_utils;
    using namespace httplib;

    using std::vector;
    using std::string;
    using std::mutex;

    class Machine
    {
    public:
        string _ip; // 后端编译运行服务器ip
        int _port; // 后端编译运行服务器端口
        uint64_t _load; // 后端编译运行服务器负载
        mutex* _mtx; // mutex禁止拷贝，所以使用指针

        Machine(const string& ip = "",const int port = 0,uint64_t load = 0)
            :_ip(ip)
            ,_port(port)
            ,_load(load)
            ,_mtx(new mutex)
        {

        }
        /**
         * @brief 增加主机负载
         */
        void increase_load()
        {
            assert(_mtx);

            _mtx->lock();
            ++_load;
            _mtx->unlock();
        }

        /**
         * @brief 减少主机负载
         */
        void decrease_load()
        {
            assert(_mtx);

            _mtx->lock();
            --_load;
            _mtx->unlock();
        }

        /**
         * @brief 获取主机负载
         */
        uint64_t get_load()
        {
            assert(_mtx);

            _mtx->lock();
            uint64_t  load = _load;
            _mtx->unlock();

            return load;
        }
    };

    const string machines_path = "./conf/machines.conf"; // 后端服务器信息文件路径
    class LoadBalance
    {
    private:
        vector<Machine> _machines;
        vector<int> _online; // 在线主机ID，ID对应 _machines下标对应元素存放的主机信息,ID从0开始
        vector<int> _offline; // 离线主机ID，ID对应 _machines下标对应元素存放的主机信息，ID从0开始
        mutex _mtx;
        /**
         * @brief 加载后端服务器信息
         *
         * @param[in] conf_path 存放后端服务器信息的文件路径
         *
         * @return true 或 false
         */
        bool load_conf(const string& conf_path)
        {
            std::ifstream in(conf_path);
            if(!in.is_open())
            {
                LOG(FATAL) << "读取配置文件：" << conf_path << "失败，无法初始化后端服务器信息" << std::endl;

                return false;
            }
            string line_content;
            size_t line = 0;
            while(getline(in,line_content))
            {
                line++;
                vector<string> info;
                StringUtil::split_string(line_content,&info,' ',true);
                if(info.size() != 2)
                {
                    LOG(WARING) << "读取第" << std::to_string(line) << "行主机信息失败，请检查主机信息格式是否合法" << std::endl;
                    continue;
                }
                string ip(info[0]);
                int port(stoi(info[1]));
                Machine machine(ip,port);

                _online.push_back(_machines.size());
                _machines.push_back(machine);
            }
            in.close();
            LOG(INFO) << "加载后端服务器信息成功" << std::endl;

            return true;
        }

    public:
        LoadBalance()
        {
            assert(load_conf(machines_path));
        }

        /**
         * @brief 选择负载最低的后端在线服务器
         *
         * @param[out] id 服务器id
         *
         * @param[out] ppm 服务器信息
         *
         * @return true 或 false
         */
        bool select_machine(int* id,Machine** ppm)
        {
            _mtx.lock();
            size_t online_num = _online.size();
            if(0 == online_num)
            {
                _mtx.unlock();
                LOG(FATAL) << "所有后端服务器异常" << std::endl;

                return false;
            }

            *id = _online[0];
            *ppm = &_machines[_online[0]];
            uint64_t min_load = _machines[*id].get_load();
            for(int i = 1;i < online_num;i++)
            {
                uint64_t current_load = _machines[_online[i]].get_load();
                if(min_load > current_load)
                {
                    *id = _online[i];
                    *ppm = &_machines[*id];
                    min_load = current_load;
                }
            }

            _mtx.unlock();

            return true;
        }

        /**
         * @brief 将某个id对应服务器从在线列表中移动到离线列表中
         *
         * @param[in] id 待下线服务器id
         *
         */
        void offline_machine(const int id)
        {
            _mtx.lock();
            for(auto iter = _online.begin();iter != _online.end();iter++)
            {
                if(*iter == id)
                {
                    /* 重置该服务器负载为0，以免后续重新上线由于负载值没有重置,而对负载均衡选择后端服务器造成影响 */
                    _machines[id]._load = 0;
                    _online.erase(iter);
                    _offline.push_back(id);
                    break;
                }
            }
            _mtx.unlock();
        }

        /**
         * @brief 将所有离线主机列表中服务器移动到在线列表中
         *
         */
        void online_machine()
        {
            _mtx.lock();
            _online.insert(_online.end(),_offline.begin(),_offline.end());
            _offline.clear();
            _mtx.unlock();
            LOG(INFO) << "重新上线所有后端服务器" << std::endl;
        }
        /**
         * @brief 打印所有在线与离线服务器信息
         *
         */
        void display_machines()
        {
            _mtx.lock();
            std::cout << "在线主机列表：" << std::endl;
            for(auto& id:_online)
            {
                string ip = _machines[id]._ip;
                int port = _machines[id]._port;
                std::cout << "{ID:" << id << " " << ip << ":" << port << "}" ;
            }
            std::cout << std::endl;
            std::cout << std::endl << "离线主机列表：" << std::endl;
            for(auto& id:_offline)
            {
                string ip = _machines[id]._ip;
                int port = _machines[id]._port;
                std::cout << "{ID:" << id << " " << ip << ":" << port << "}";
            }
            std::cout << std::endl;

            _mtx.unlock();
        }


    };
    class Control
    {
    private:
        Model _model;
        View _view;
        LoadBalance _load_balance;
    public:
        /**
         * @brief 构建题目列表网页内容
         *
         * @param[out] html_content 存放题目列表网页内容
         *
         * @return true 或 false
         */
        bool all_questions(string* html_content)
        {
            html_content->clear();
            bool res = true;
            vector<Question> questions;
            if(_model.get_all_questions(&questions)) // 获取数据
            {
                _view.expand_questions_list_html(questions,html_content); // 使用数据对网页渲染
            }
            else
            {
                *html_content = "获取题目列表失败，无法构建网页";
                res = false;
            }

            return res;
        }

        /**
         * @brief 构建某个题目网页内容
         *
         * @param[in] number 题目编号
         *
         * @param[out] html_content 存放某个题目网页内容
         *
         * @return true 或 false
         */
        bool one_question(const string& number,string* html_content)
        {
            html_content->clear();
            bool res = true;
            Question question;
            if(_model.get_one_question(number,&question))
            {
                _view.expand_question_html(question,html_content);
            }
            else
            {
                *html_content = "编号为：" + number + " 题目不存在";
                res = false;
            }

            return res;
        }

        /**
         * @brief 对用户提交的某个题目进行判题
         *
         * @param[in] number 题目编号
         *
         * @param[in] in_json 用户提交过来的代码，不包含测试代码
         *
         * @param[out] out_json 编译运行结果的json
         *
         * @return true 或 false
         */
        void judge(const string& number,const string& in_json,string* out_json)
        {
            /* 1. 获取题目信息从而得到对应题目测试代码，用于和用户提交的代码拼接 */
            Question question;
            _model.get_one_question(number,&question);

            /* 2. 反序列化in_json */
            Json::Reader reader;
            Json::Value in_root;
            reader.parse(in_json,in_root);
            string basic_code = in_root["basic_code"].asString();

            /* 3. 将用户提交的基本代码与测试用例代码合并，构建用于请求的json */
            Json::Value out_root;
            /* basic_code与test_code之间加换行符是防止用户提交过来的basic_code代码中最后一行语句没有
             * 加换行符，导致test_code第一行宏定义与basic_code最后一行语句拼接成一行，造成编译错误
             * */
            out_root["code"] = basic_code + '\n' + question.test_code;
            out_root["input"] = in_root["input"].asString();
            out_root["cpu_limit"] = question.cpu_limit;
            out_root["mem_limit"] = question.mem_limit;
            Json::FastWriter writer;
            std::string request = writer.write(out_root);

           /* 4. 负载均衡选择后端服务器进行处理 */
           while(true)
           {
               int id = 0;
               Machine* pmachine = nullptr;
               if(!_load_balance.select_machine(&id,&pmachine))
               {
                   break;
               }

               /* 5. 构建http请求报文,向后端服务器进行请求  */
               string ip = pmachine->_ip;
               int port = pmachine->_port;
               uint64_t load = pmachine->_load;
               Client client(ip,port);
               pmachine->increase_lMoad(); // 服务器负载+1
               LOG(INFO) << "选择ID：" << id << " 后端服务器处理，服务器信息：[" << ip << ":" << port << " load: " << pmachine->get_load() << "]" << std::endl;

               if(auto res = client.Post("/compile_run", request, "application/json;charset=utf-8"))
               {
                   /* 用响应报文构建out_json */
                   if(res->status == 200)
                   {
                       *out_json = res->body;
                       pmachine->decrease_load();
                       LOG(INFO) << "后端服务器完成一次编译运行任务" << std::endl;
                       break;
                   }
                   pmachine->decrease_load(); // 服务器负载-1
               }
               else // 请求后端服务器失败，对服务器离线
               {
                   _load_balance.offline_machine(id);

                   LOG(INFO) << "请求ID：" << id << " 的后端服务器出现异常已经离线，服务器详细信息：[" << ip << ":" << port << " load: " << load << "]" << std::endl;
                   _load_balance.display_machines();
               }
           }
        }

        /**
         * @brief 恢复所有离线服务器
         *
         */
        void recover()
        {
            _load_balance.online_machine();
        }
    };
}
